package pl.edu.pb.wi.cookbook.service;

import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.logging.Logger;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import pl.edu.pb.wi.cookbook.entity.Category;
import pl.edu.pb.wi.cookbook.entity.CookbookRecipe;
import pl.edu.pb.wi.cookbook.entity.Recipe;
import pl.edu.pb.wi.cookbook.entity.User;
import pl.edu.pb.wi.cookbook.entity.UserRecipePoints;
import pl.edu.pb.wi.cookbook.util.FullTextSearchUtil;

import com.googlecode.objectify.Key;
import com.googlecode.objectify.Objectify;
import com.googlecode.objectify.ObjectifyFactory;
import com.googlecode.objectify.Query;

@Service
public class RecipeService implements IFullTextSearch<Recipe> {
    private static final Logger log = Logger.getLogger(RecipeService.class
            .getName());

    private Objectify oby;

    @Autowired
    public RecipeService(ObjectifyFactory objectifyFactory) {
        this.oby = objectifyFactory.begin();
    }

    public Recipe createRecipe(Key<User> author, Key<Category> category,
            String name, String description, String shortDescription,
            String ingredients, int prepareTime, int servings, String imageUrl,
            boolean isPrivate) {
        Recipe newRecipe = new Recipe();
        newRecipe.setAuthor(author);
        newRecipe.setCategory(category);
        newRecipe.setName(name);
        newRecipe.setShortDescription(shortDescription);
        newRecipe.setIngredients(ingredients);
        newRecipe.setPrepareTime(prepareTime);
        newRecipe.setServings(servings);
        newRecipe.setImageUrl(imageUrl);
        newRecipe.setPrivate(isPrivate);
        newRecipe.setPositiveVoteCount(0);
        newRecipe.setNegativeVoteCount(0);
        newRecipe.setDescription(description);
        newRecipe.updateFts();

        oby.put(newRecipe);

        return newRecipe;
    }

    public Recipe createRecipe(Long authorId, Long categoryId, String name,
            String description, String shortDescription, String ingredients,
            int prepareTime, int servings, String imageUrl, boolean isPrivate) {
        Recipe newRecipe = new Recipe();
        newRecipe.setAuthor(new Key<User>(User.class, authorId));
        newRecipe.setCategory(categoryId == null ? null : new Key<Category>(
                Category.class, categoryId));
        newRecipe.setName(name);
        newRecipe.setShortDescription(shortDescription);
        newRecipe.setIngredients(ingredients);
        newRecipe.setPrepareTime(prepareTime);
        newRecipe.setServings(servings);
        newRecipe.setImageUrl(imageUrl);
        newRecipe.setPrivate(isPrivate);
        newRecipe.setPositiveVoteCount(0);
        newRecipe.setNegativeVoteCount(0);
        newRecipe.setDescription(description);
        newRecipe.updateFts();

        oby.put(newRecipe);

        return newRecipe;
    }

    public void removeRecipe(Recipe recipe) {
        List<Key<CookbookRecipe>> recipesConnectedWithCookbook = oby
                .query(CookbookRecipe.class)
                .filter("recipse",
                        new Key<Recipe>(Recipe.class, recipe.getId()))
                .listKeys();
        if (recipesConnectedWithCookbook != null) {
            oby.delete(recipesConnectedWithCookbook);
        }

        oby.delete(recipe);
    }

    public void removeRecipe(Key<Recipe> recipe) {
        List<Key<CookbookRecipe>> recipesConnectedWithCookbook = oby
                .query(CookbookRecipe.class).filter("recipse", recipe)
                .listKeys();
        if (recipesConnectedWithCookbook != null)
            oby.delete(recipesConnectedWithCookbook);
        oby.delete(recipe);
    }

    public Key<Recipe> updateRecipe(Recipe recipe) {
        recipe.updateFts();

        return oby.put(recipe);
    }

    public List<Recipe> getRecipesFromCategory(Category category,
            boolean isPrivate) {
        return oby
                .query(Recipe.class)
                .filter("category",
                        new Key<Category>(Category.class, category.getId()))
                .filter("isPrivate", isPrivate).list();
    }

    public List<Recipe> getUserRecipes(Key<User> user) {
        return oby.query(Recipe.class).filter("author", user).list();
    }

    public List<Recipe> getUserRecipes(User user, boolean isPrivate) {
        return oby.query(Recipe.class)
                .filter("author", new Key<User>(User.class, user.getId()))
                .filter("isPrivate", isPrivate).list();
    }

    public Recipe getRecipe(Long id) {
        return oby.get(new Key<Recipe>(Recipe.class, id));
    }

    public List<Recipe> getUserRecipes(User user) {
        return oby.query(Recipe.class)
                .filter("author", new Key<User>(User.class, user.getId()))
                .list();
    }

    public List<Recipe> getUserRecipesFromCategory(User user, Category cat) {
        return oby
                .query(Recipe.class)
                .filter("author", new Key<User>(User.class, user.getId()))
                .filter("category",
                        new Key<Category>(Category.class, cat.getId())).list();
    }

    public List<Category> getCategoriesWithUserRecipes(User user) {
        List<Recipe> recipes = getUserRecipes(user);

        Set<Key<Category>> categories = new LinkedHashSet<Key<Category>>();
        for (Recipe rec : recipes)
            categories.add(rec.getCategory());

        return new LinkedList<Category>(oby.get(categories).values());
    }

    public List<Recipe> getRecipesFromCategory(Key<Category> category) {
        return oby.query(Recipe.class).filter("category", category).list();
    }

    public UserRecipePoints rateRecipe(User user, Recipe recipe, int points) {
        Key<User> userKey = new Key<User>(User.class, user.getId());
        Key<Recipe> recipeKey = new Key<Recipe>(Recipe.class, recipe.getId());

        UserRecipePoints urp = oby.query(UserRecipePoints.class)
                .filter("user", userKey).filter("recipe", recipeKey).get();

        if (urp == null) {
            urp = new UserRecipePoints();
            urp.setUser(userKey);
            urp.setRecipe(recipeKey);
            urp.setPoints(points);

            Recipe rec = oby.get(recipeKey);
            if (points > 0)
                rec.setPositiveVoteCount(rec.getPositiveVoteCount() + 1);
            else
                rec.setNegativeVoteCount(rec.getNegativeVoteCount() + 1);

            oby.put(rec);
        } else {
            urp.setPoints(points);
        }

        oby.put(urp);

        return urp;
    }

    public UserRecipePoints rateRecipe(Key<User> user, Key<Recipe> recipe,
            int points) {
        UserRecipePoints urp = oby.query(UserRecipePoints.class)
                .filter("user", user).filter("recipe", recipe).get();

        if (urp == null) {
            urp = new UserRecipePoints();
            urp.setUser(user);
            urp.setRecipe(recipe);
            urp.setPoints(points);

            Recipe rec = oby.get(recipe);
            if (points > 0)
                rec.setPositiveVoteCount(rec.getPositiveVoteCount() + 1);
            else
                rec.setNegativeVoteCount(rec.getNegativeVoteCount() + 1);

            oby.put(rec);
        } else {
            urp.setPoints(points);
        }

        oby.put(urp);

        return urp;
    }

    public boolean isRecipeRatedByUser(User user, Recipe recipe) {
        Key<UserRecipePoints> urp = oby
                .query(UserRecipePoints.class)
                .filter("user", new Key<User>(User.class, user.getId()))
                .filter("recipe", new Key<Recipe>(Recipe.class, recipe.getId()))
                .getKey();

        return urp == null ? Boolean.FALSE : Boolean.TRUE;
    }

    public boolean isRecipeRatedByUser(Key<User> user, Key<Recipe> recipe,
            int points) {
        Key<UserRecipePoints> urp = oby.query(UserRecipePoints.class)
                .filter("user", user).filter("recipe", recipe).getKey();

        return urp == null ? Boolean.FALSE : Boolean.TRUE;
    }

    public Integer getRecipePoints(Recipe recipe) {
        List<UserRecipePoints> urps = oby
                .query(UserRecipePoints.class)
                .filter("recipe", new Key<Recipe>(Recipe.class, recipe.getId()))
                .list();

        int sum = 0;
        for (UserRecipePoints urp : urps) {
            sum += urp.getPoints();
        }

        return sum;
    }

    public Integer getRecipePoints(Key<Recipe> recipe) {
        List<UserRecipePoints> urps = oby.query(UserRecipePoints.class)
                .filter("recipe", recipe).list();

        int sum = 0;
        for (UserRecipePoints urp : urps) {
            sum += urp.getPoints();
        }

        return sum;
    }

    @Override
    public List<Recipe> search(String queryString) {
        Query<Recipe> query = oby.query(Recipe.class);

        Set<String> queryTokens = FullTextSearchUtil
                .getTokensForIndexingOrQuery(queryString,
                        FullTextSearchUtil.MAXIMUM_NUMBER_OF_WORDS_TO_SEARCH);
        List<String> queryTokensList = new ArrayList<String>(queryTokens);

        for (String token : queryTokensList) {
            log.info("Token: " + token);
            query.filter("fts", token);
        }

        List<Recipe> result = new ArrayList<Recipe>();
        for (Recipe recipe : query) {
            result.add(recipe);
        }

        return result;
    }
}
